/**
 * NoxCanvas
 * 
 * @param canvasEl string - id of element
 * @param data string|Array - string for url data or Array of NoxImages
 *
 * @author Lukas Krocek
 * @copyright 2011-2012
 */

(function (window, undefined) {
	/** 
	 * @var int interval of rendering in ms
	 */
	var interval = 20,
	
		/** 
		 * @var Object for moving camera
		 */
		cameraMove = {
			x : 0,
			y : 0
		},
		/** @var Array this.objects in canvas */
		objects = [],
	
	
		/**
		 * reference on object by time interval
		 * @var Object
		 */
		objByTime = {},

		/** 
		 * @var bool - if true than canvas will be refresh
		 */
		isValid = false,
	
		/** @var int - fps counter */
		fps = 0,
	
		/** @var Object of HTMLElements */
		elements = {
			/** @var HTMLElement for fps counting */
			fps: null,
			/** @var HTMLCanvasElement */
			cns: null
		};

		/** @var bool */
		mouseClick = false, // TODO
	
		/**
		 * @var canvas context
		 */
		ctx = null,
	
		/**
		 * Clear a canvas
		 */
		clear = function() {
			ctx.clearRect(0, 0, elements.cns.width, elements.cns.height);
		},
	
		/** 
		 * Prevents scroll on target
		 * @param object e
		 */
		stopWheel = function (e) {
		    if(!e){e = window.event;} /* IE7, IE8, Chrome, Safari */
		    if(e.preventDefault) {e.preventDefault();} /* Chrome, Safari, Firefox */
		    e.returnValue = false; /* IE7, IE8 */
		},
		
		/**
		 * Constructor
		 */
		init = function (canvasEl, data) {
			elements.cns = canvasEl;
	
			if(elements.cns.getContext) {
				ctx = elements.cns.getContext('2d');
			} else {
				// Exception
				alert('Canvas wasn\'t initialized!');
			}
			elements.prog = document.querySelector('progress');
	
			if( typeof data === 'object' && data.length > 0) {
				objects = data; 
			} else {
				xhr(data, function (xmlhttp) {
					if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
						responseData = JSON.parse(xmlhttp.responseText);
						for (var i = 0,l=responseData.length; i < l; i++) {
							if (responseData[i].pos === undefined) responseData[i].pos = {x:0,y:0};
							if (responseData[i].time === undefined) responseData[i].time = 1000;
							if (responseData[i].frames === undefined) responseData[i].frames = 1;
							objects[objects.length] = new NoxImage(
								responseData[i].image,
								responseData[i].frames,
								responseData[i].pos,
								responseData[i].time,
								responseData[i].reverse
							);
						}
						isValid = true;
					}
				});
			}
			
			// Public functions
			
			/**
			 * mouseMove
			 * Allow canvas move by mouse click and drag
			 *
			 * @param clickTyp string - left|middle|right - Default right
			 * 
			 * @return this - fluent
			 */
			this.mouseMove = function(cT) {
				var clickType;
				switch (cT) {
					case 'middle': clickType = 1; break;
					case 'right': clickType = 2; break;
					default: clickType = 0; break;
				}
				elements.cns.addEventListener('mousedown', function(e) {
					if (e.button == clickType) {
						if(!e.offsetX) {
							mouseClick = {
								x : e.layerX,
								y : e.layerY
							};
						} else {
							mouseClick = {
								x : e.offsetX,
								y : e.offsetY
							};
						}
					}
				}, false);
				elements.cns.addEventListener('mouseup', function(e) {
					if (e.button == clickType) {
						mouseClick = false;
					}
				}, false);
				elements.cns.addEventListener('mousemove', function(e) {
					if(mouseClick !== false) {
						if(!e.offsetX) {
							cameraMove.x += (e.layerX - mouseClick.x);
							cameraMove.y += (e.layerY - mouseClick.y);
							mouseClick = {
								x : e.layerX,
								y : e.layerY
							};
						} else {
							cameraMove.x += (e.offsetX - mouseClick.x);
							cameraMove.y += (e.offsetY - mouseClick.y);
							mouseClick = {
								x : e.offsetX,
								y : e.offsetY
							};
						}
					}
				}, false);
				return this;
			}
			
			/**
			 * start
			 * Start rendering on canvas
			 * 
			 * @param callback - calling when canvas start render
			 */
			this.start = function (callback) {
			    if (isLoaded()) {
			        render();
			        animate();
			        renderFps(document.getElementById('fps'));
			        if (typeof(callback) === 'function') {
			        	callback();
			        }
			    } else {
			    	var self = this;
		            setTimeout(function () {self.start(callback)}, 500);
			    }
			    return this;
			}
					
			/**
			 * elementProtection
			 * Protect element on site
			 * 
			 * @param noScroll
			 * 
			 * @return this - fluent
			 */
			this.elementProtection = function (noScroll) {
				
			    elements.cns.tabIndex = 0;
			    elements.cns.focus();
			    /** wheel stop */
				/* IE7, IE8 */
			    elements.cns.onmousewheel = stopWheel;
			    /* Chrome, Safari, Firefox */
			    if(elements.cns.addEventListener){
		            elements.cns.addEventListener('DOMMouseScroll', stopWheel, false);
			    }
			    return this;
			}
	
		},
				
		/**
		 * wheelControl
		 * Wheel control on HTMLCanvasElement
		 * 
		 * @event mousewheel for HTML
		 */
		wheelControl = function () {
		    elements.cns.addEventListener('mousewheel', function (e) {
		        if (e.wheelDelta != 0) {
		            scrollStep = Math.round(e.wheelDelta / 120);
		        }
		        return false;
		    },false);
		},
	
		/**
		 * Checking for ready state of all this.objects
		 */
		isLoaded = function() {
			l = objects.length;
			for(var i = 0; i < l; i++) {
				if(!objects[i].isReady())
					return false;
			}
			if (l > 0) {
				return true;
			}
			return false;
		},
		
		
		/**
		 * Rendering repeatable yourself
		 */
		render = function() {
			redraw();
			setTimeout(render, interval);
		},
		
		/**
		 * Redraw a canvas
		 */
		redraw = function() {
			l = objects.length;
			clear();
			for(var i = 0; i < l; i++) {
				objects[i].move(cameraMove.x, cameraMove.y);
				objects[i].render(ctx);
			}
			cameraMove = {
				x : 0,
				y : 0
			};
			fps++;
		},
	
		
		/**
		 * animate
		 * Animate all objects at once
		 * 
		 * @param time int|void - interval
		 */
		animate = function (time) {
			if (time === undefined) {
				// prepare objects
				for (var i=0;i<objects.length;i++) {
					inter = objects[i].getInterval();
					if (inter) {
						if (!objByTime[inter]) {
							objByTime[inter] = [];
						}
						objByTime[inter][objByTime[inter].length] = i;
					}
				}
				// cycle all intervals
				for ( var time in objByTime) {
					animate(time);
				}
			} else {
				// animate all objects at once
				for (var i in objByTime[time]) {
					objects[objByTime[time][i]].animate();
				}
				// cycleself
				setTimeout(function () {animate(time)}, time);
			}
		},
		
		/**
		 * Counting a real fps to element
		 *
		 * @required this.elements.fps
		 */
		renderFps = function(elFps) {
			if(elements.fps !== null) {
				elements.fps.innerHTML = Math.round(fps / 2);
				fps = 0;
					setTimeout(renderFps, 2000);
			} else if(elFps !== undefined) {
				elements.fps = elFps;
				setTimeout(renderFps, 2000);
			}
		},
		
		/**
		 * xhr
		 * Xml Http Request
		 * 
		 * @deprecated not successful
		 * @param url string
		 * @param callback Function
		 */
		xhr = function (url, callback) {
			var xmlhttp = new XMLHttpRequest();
			if (!xmlhttp) {
				return false;
			}
			xmlhttp.open('post', url);
			xmlhttp.onreadystatechange = function () {
				callback(xmlhttp);
			};
			headers = {};
			headers['X-Requested-With'] = headers['X-Requested-With'] || 'XMLHttpRequest';
			for (var key in headers) {
				xmlhttp.setRequestHeader(key, headers[key]);
			}
			xmlhttp.send(null);
		}
	
	window.nox = init;
})(window);
